package org.egrit;

import java.awt.Point;
import java.awt.Rectangle;

import org.egrit.util.Args;

/**
 * Represents the bounds of a single grid cell. Using this class is more
 * efficient than representing the cell with the more general 
 * {@linkplain RectGridBounds}.
 * 
 * @author michael
 */
public class CellGridBounds implements IGridBounds {
    
    private final Point cellPos;

    /*
     * NOTE: there is no no-arg constructor because we don't want
     * an empty bounds instance of this class.
     */
    
    /**
     * Creates a new bounds instance for the given cell position.
     * 
     * @param gridX minimum grid X ordinate of included cell
     * @param gridY maximum grid X ordinate of included cell
     */
    public CellGridBounds(int gridX, int gridY) {
        cellPos = new Point(gridX, gridY);
    }
    
    /**
     * Creates a new bounds instance for the given cell position.
     * 
     * @param pos grid cell position
     */
    public CellGridBounds(Point pos) {
        Args.notNull("pos", pos);
        this.cellPos = new Point(pos);
    }
    
    @Override
    public boolean isEmpty() {
        return false;
    }
    
    @Override
    public int getWidth() {
        return 1;
    }
    
    @Override
    public int getHeight() {
        return 1;
    }

    @Override
    public int getArea() {
        return 1;
    }
    
    @Override
    public int getMinGridX() {
        return cellPos.x;
    }

    @Override
    public int getMinGridY() {
        return cellPos.y;
    }

    @Override
    public Point getMinCellPos() {
        return new Point(cellPos);
    }
    
    @Override
    public int getMaxGridX() {
        return cellPos.x;
    }
    
    @Override
    public int getMaxGridY() {
        return cellPos.y;
    }
    
    @Override
    public Point getMaxCellPos() {
        return new Point(cellPos);
    }
    
    @Override
    public Rectangle getOuterRect() {
        return new Rectangle(cellPos.x, cellPos.y, 1, 1);
    }
    
    @Override
    public boolean containsCell(Point gridPos) {
        Args.notNull("gridPos", gridPos);
        return cellPos.equals(gridPos);
    }
    
    @Override
    public boolean containsCell(int gridX, int gridY) {
        return cellPos.x == gridX && cellPos.y == gridY;
    }

    @Override
    public IGridBounds intersection(IGridBounds other) {
        if (other != null && other.containsCell(cellPos)) {
            return new CellGridBounds(cellPos);
        }
        
        return GridBoundsFactory.create();
    }

    @Override
    public IGridBounds intersection(Rectangle rect) {
        if (rect != null && rect.contains(cellPos)) {
            return new CellGridBounds(cellPos);
        } 

        return GridBoundsFactory.create();
    }
    
    @Override
    public boolean intersects(IGridBounds other) {
        if (other == null || other.isEmpty()) {
            return false;
        }
        
        return other.containsCell(cellPos);
    }
    
    @Override
    public boolean contains(IGridBounds other) {
        if (other == null || other.isEmpty()) {
            return false;
        }
        
        Rectangle otherRect = other.getOuterRect();

        return (otherRect.x == cellPos.x && otherRect.y == cellPos.y &&
                otherRect.width == 1 && otherRect.height == 1);
    }

    /**
     * Creates and returns an iterator for all cell positions within the bounds.
     * 
     * @return a new iterator
     */
    @Override
    public GridBoundsIterator getIterator() {
        return new GridBoundsIterator(this);
    }

    /**
     * Tests equality this instance and another object. 
     * <p>
     * Note that an {@linkplain IGridBounds} object can be equal to this 
     * instance even if it is of a different class since equality is assessed
     * on the basis of bounds location and size.
     * 
     * @param obj the other object
     * 
     * @return true if the other object represents the same bounds as 
     *     this instance
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (!(obj instanceof IGridBounds)) {
            return false;
        }
        
        IGridBounds other = (IGridBounds) obj;
        
        if (other.isEmpty()) {
            return false;
        }
        
        return other.getArea() == 1 && other.getMinCellPos().equals(cellPos);
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 31 * hash + this.cellPos.hashCode();
        return hash;
    }

    @Override
    public String toString() {
        return String.format("GridBounds[(%d,%d) to (%d,%d)]",
                getMinGridX(),
                getMinGridY(),
                getMaxGridX(),
                getMaxGridY());
    }
    
}
